package main

/*
#include <stdlib.h>

typedef int (qsort_cmp_func_t)(const void* a, const void* b);

int cgoQSortCompare(void* , void* );
*/
import "C"
import (
	"fmt"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"unsafe"
)

type GoQSortCompareInfo struct {
	base     unsafe.Pointer
	elemNum  int
	elemSize int
	less     func(a, b int) bool
}

var m sync.Map

//export cgoQSortCompare
func cgoQSortCompare(a, b unsafe.Pointer) C.int {
	goQSortCompareInfoTmp, _ := m.Load(GetGoId())
	goQSortCompareInfo := goQSortCompareInfoTmp.(GoQSortCompareInfo)
	var (
		// array memory is locked
		base     = uintptr(goQSortCompareInfo.base)
		elemSize = uintptr(goQSortCompareInfo.elemSize)
	)

	i := int((uintptr(a) - base) / elemSize)
	j := int((uintptr(b) - base) / elemSize)

	switch {
	case goQSortCompareInfo.less(i, j): // v[i] < v[j]
		return -1
	case goQSortCompareInfo.less(j, i): // v[i] > v[j]
		return +1
	default:
		return 0
	}

}

func Slice(slice interface{}, less func(a, b int) bool) {
	sv := reflect.ValueOf(slice)
	if sv.Kind() != reflect.Slice {
		panic(fmt.Sprintf("qsort called with non-slice value of type %T", slice))
	}
	if sv.Len() == 0 {
		return
	}

	goQSortCompareInfo := GoQSortCompareInfo{}

	// baseMem = unsafe.Pointer(sv.Index(0).Addr().Pointer())
	// baseMem maybe moved, so must saved after call C.fn
	goQSortCompareInfo.base = unsafe.Pointer(sv.Index(0).Addr().Pointer())
	goQSortCompareInfo.elemNum = sv.Len()
	goQSortCompareInfo.elemSize = int(sv.Type().Elem().Size())
	goQSortCompareInfo.less = less

	m.Store(GetGoId(), goQSortCompareInfo)
	defer m.Delete(GetGoId())
	C.qsort(
		goQSortCompareInfo.base,
		C.size_t(goQSortCompareInfo.elemNum),
		C.size_t(goQSortCompareInfo.elemSize),
		//C.cgo_qsort_compare,
		(*[0]byte)(C.cgoQSortCompare),
	)
}

func GetGoId() int64 {
	var (
		buf [64]byte
		n   = runtime.Stack(buf[:], false)
		stk = strings.TrimPrefix(string(buf[:n]), "goroutine")
	)

	idField := strings.Fields(stk)[0]
	id, err := strconv.Atoi(idField)
	if err != nil {
		panic(fmt.Errorf("can not get goroutine id: %v", err))
	}

	return int64(id)

}
